;; should make a package and let asd do this for me
;; in the meantime
;; - we fetch url with dexador's get
;; - and handle json with jsown:parse
(in-package :divvy)

;; for djula
(djula:add-template-directory (asdf:system-relative-pathname "divvy" "templates/"))

(defparameter +template+ (djula:compile-template* "template.html"))

(defparameter +stations-url+
  "https://feeds.divvybikes.com/stations/stations.json")

(defvar *current-stations* nil)

(defun fetch-station-json ()
  (jsown:parse (dex:get +stations-url+)))

(defclass station ()
  ((id
    :initarg :id
    :reader id
    :type integer)
   (station-name
    :initarg :station-name
    :reader station-name
    :type string)
   (available-docks
    :initarg :available-docks
    :reader available-docks
    :type integer)
   (total-docks
    :initarg :total-docks
    :reader total-docks
    :type integer)
   (latitude
    :initarg :latitude
    :reader latitude)
   (logitude
    :initarg :longitude
    :reader longitude)
   (status-value
    :initarg :status-value
    :reader status-value
    :type string)
   (status-key
    :initarg :status-key
    :reader status-key
    :type integer)
   (status
    :initarg :status
    :reader status)
   (available-bikes
    :initarg :available-bikes
    :reader available-bikes
    :type integer)
   (street-address
    :initarg :street-address
    :reader street-address
    :type string)
   (city
    :initarg :city
    :initform "Chicago"
    :reader city
    :type string)
   (zipcode
    :initarg :zipcode
    :reader zipcode
    :type string)
   (last-communication-time
    :initarg :last-communication-time
    :reader last-communication-time)
   (is-renting?
    :initarg :is-renting
    :reader is-renting?
    :type boolean))
  (:documentation "A station from the divvy feed"))

(defun string-assoc (keyname alist)
  "given an alist with string keys, make assoc work"
  (cdr (assoc-if (lambda (name) (string-equal name keyname)) alist)))

(defun make-station-instance (station-list)
  "given a list of stations from json
   generate a list of station instances by reading the data
   provided by the feed"
  ;; strip :obj if it's the first item so we have an alist
  (let ((pairs
	 (if (eq :OBJ (first station-list))
	     (rest station-list)
	     station-list )))
    (flet ((-lookup (keyname)
	     (string-assoc keyname pairs)))
      (make-instance
       'station
       :id (-lookup "id")
       :station-name (-lookup "stationName")
       :total-docks (-lookup "totalDocks")
       :available-docks (-lookup "availableDocks")
       :available-bikes (-lookup "availableBikes")
       :status (-lookup "status")
       :status-key (-lookup "statusKey")
       :status-value (-lookup "statusValue")
       :last-communication-time (-lookup "lastCommunicationTime")
       :latitude (-lookup "latitude")
       :longitude (-lookup "longitude")
       :street-address (format nil "~a ~a"
			       (-lookup "stAddress1")
			       (-lookup "stAddress2"))
       :city (-lookup "city")
       :zipcode (-lookup "postalCode")
       :is-renting (-lookup "is_renting")))))

(defun refresh! ()
  "sets the current-stations variable to a list of station instances"
  (setf *current-stations*
	(mapcar
	 #'make-station-instance
	 (rest
	  (third
	   (fetch-station-json))))))

;; things I'm interested in usually
;; I commute from rogers park to the west loop
;; along broadway and halsted, or clark
(defvar known-stations
  '(
    "Glenwood Ave & Morse Ave"
    "Halsted St & Dickens Ave"
    "Green St & Randolph St"
    "Sheridan Rd & Montrose Ave"
    "Benson Ave & Church St"
    "Halsted St & Wrightwood Ave"
    "Clarendon Ave & Leland Ave"
    "Clark St & Lunt Ave"
    "Sheffield Ave & Wrightwood Ave"
    "Sheridan Rd & Irving Park Rd"
    "Broadway & Berwyn Ave"
    "Sheridan Rd & Lawrence Ave"
    "Broadway & Argyle St"
    "Dayton St & North Ave"
    "Lincoln Ave & Leavitt St"
    "Warren Park East"
    "Clark St & Leland Ave"
    "Leavitt St & Lawrence Ave"
    "Damen Ave & Wellington Ave"
    "Greenview Ave & Diversey Pkwy"
    "Southport Ave & Clark St"
    "Sheffield Ave & Wrightwood Ave"
    "Clark St & Grace St"
    "Broadway & Granville Ave"
    "Broadway & Wilson Ave"
    "Winthrop Ave & Lawrence Ave"
    "Clark St & Elmdale Ave"
    "Clark St & Drummond Pl"
    "State St & Van Buren St"
    "Lakeview Ave & Fullerton Pkwy"
    "Marine Dr & Ainslie St"
    "Dearborn Pkwy & Delaware Pl"
    "Broadway & Sheridan Rd"
    "Desplaines St & Randolph St"
    "Sheridan Rd & Loyola Ave"
    "Lakefront Trail & Bryn Mawr Ave"
    "Streeter Dr & Grand Ave"
    "Montrose Harbor"
    "St. Clair St & Erie St"
    "Broadway & Thorndale Ave"
    "Wabash Ave & Grand Ave"
    "Lake Shore Dr & Ohio St"
    "Clinton St & Washington Blvd"
    "Clark St & Schiller St"
    ))

(defun interesting-stations () 
  (remove-if-not 
   (lambda (station)
     (member (station-name station)
	     known-stations
	     :test #'string-equal))
   *current-stations*))

(defun current-time ()
  (local-time:format-rfc1123-timestring nil (local-time:clock-now T)))

(defun render ()
  (djula:render-template*
   +template+ nil
   :stations (interesting-stations)
   :now (current-time)))

(defun write-output ()
  ;; reload data
  (refresh!)
  ;; emit html
  (with-open-file (*standard-output*
    #P"/tmp/divvy.html"
    :direction :output
    :if-exists :supersede
    :if-does-not-exist :create)
    (format t (render))))
